#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

#include "pmsq.h"
#include "qlist.h"
/*
 *  gcc -g -O0 qlist.c pmsq.c -o pmq -lpthread && ./pmq
 */
static void init_queue(QNode *qHead);
static void pmq_print_node(QNode *qnode);
static void test_send_receive();
static void test_enqueue();
static void test_dequeue();
static void test_send_receive_struct();

PriorityQueue pmq = {0};

void pmq_init(PriorityQueue *pmq)
{
    for (int i = 0; i < PRIORITY_MAX; i++)
    {
        pmq->queues[i] = (QNode *)malloc(sizeof(QNode));
        // pmq->queues[i]->data = NULL;
        pmq->queues[i]->next = NULL;
        pmq->queues[i]->data_len = 0;
    }

    init_queue(pmq->queues[PRIORITY_FREE]);
    pmq->free_queue_size = QUEUE_SIZE;

    pthread_mutex_init(&pmq->mutex, NULL);
    pthread_cond_init(&pmq->not_empty, NULL);
    pthread_cond_init(&pmq->not_full, NULL);

    printf("Priority queue initialized.\n");
}

static void init_queue(QNode *qHead)
{
    for (int i = 0; i < QUEUE_SIZE; i++)
    {
        insert_tail(&qHead, NULL, 0);
    }
}

void pmq_send(void *msg, size_t msg_size, int priority)
{
    pmq_enqueue(&pmq, msg, msg_size, priority);
}

void pmq_receive(void *msg)
{
    int priority = 0;
    int data_len = 0;
    void *data = pmq_dequeue(&pmq, &data_len, &priority);

    memcpy(msg, data, data_len);
}

void pmq_enqueue(PriorityQueue *pmq, void *msg, size_t msg_size, int priority)
{
    printf("pmq_enqueue start\n");

    pthread_mutex_lock(&pmq->mutex);
    QNode *idle_node = NULL;
    while (pmq->free_queue_size == 0)
    {
        printf("free queue is empty, wait for slot...\n");
        pthread_cond_wait(&pmq->not_full, &pmq->mutex);
    }

    printf("get the slot from free queue, dequeue a node\n");

    idle_node = pick_tail_node(&pmq->queues[PRIORITY_FREE], 0);

    insert_node_to_head(&pmq->queues[priority], idle_node, msg, msg_size);
    pmq->free_queue_size--;

    pthread_cond_signal(&pmq->not_empty);
    pthread_mutex_unlock(&pmq->mutex);

    printf("pmq_enqueue end\n");
}

void *pmq_dequeue(PriorityQueue *pmq, int *data_size, int *priority)
{
    void *ret = NULL;
    printf("pmq_dequeue start\n");
    QNode *deq_node = NULL;
    pthread_mutex_lock(&pmq->mutex);

    while (1)
    {
        for (int i = PRIORITY_HIGH; i < PRIORITY_MAX; ++i)
        {
            QNode *q = pmq->queues[i];
            if (q && q->data)
            {
                deq_node = pick_tail_node(&q, 1);
                if (deq_node == NULL)
                {
                    printf("messae queue[%d] has empty, find node in next queue\n", i);
                    continue;
                }
                else
                {
                    ret = deq_node->data;
                    printf("dequeue node's data: %s\n", ret);
                    *priority = i;
                    *data_size = deq_node->data_len;
                    pthread_cond_signal(&pmq->not_full);
                    pthread_mutex_unlock(&pmq->mutex);

                    pmq->queues[i] = q;
                    insert_node_to_tail(&pmq->queues[PRIORITY_FREE], deq_node, NULL, 0);
                    pmq->free_queue_size++;

                    break;
                }
            }
            else
            {
                continue;
            }
        }
        /* look through all the queue, can't find the avaialbe message, hold on to wait produer's product */
        if (deq_node == NULL)
        {
            printf("no available message, just waiting...\n");
            pthread_cond_wait(&pmq->not_empty, &pmq->mutex);
        }
        else
        {
            break;
        }
    }

    return ret;
}

void pmq_free(PriorityQueue pmq)
{
    // Free the memory allocated for the priority queue
    for (int i = 0; i < PRIORITY_MAX; i++)
    {
        free_list(pmq.queues[i], free_node_data);
    }
}

void pmq_destroy(PriorityQueue pmq)
{
    // Destroy the priority queue
    for (int i = 0; i < PRIORITY_MAX; i++)
    {
        free(&pmq.queues[i]);
    }
}

void pmq_print(PriorityQueue *pmq)
{
    printf("pmq_print starting...\n");
    for (int i = PRIORITY_HIGH; i < PRIORITY_MAX; i++)
    {
        printf("Queue %d:\n", i);
        print_list(pmq->queues[i], pmq_print_node);
    }
}

static void pmq_print_node(QNode *qnode)
{
    char *str = (char *)qnode->data;
    printf("%s\n", str);
}

int main()
{
    // test_link();
    // test_enqueue();
    test_dequeue();
    // test_send_receive();
    // test_send_receive_struct();

    return 0;
}

typedef struct
{
    int age;
    char name[256];
} TestStruct_t;

static void test_send_receive_struct()
{
    pmq_init(&pmq);
    printf("queue's node count: %d, size: %ld\n", list_node_count(pmq.queues[PRIORITY_FREE]), pmq.free_queue_size);

    TestStruct_t test_struct = {0};
    test_struct.age = 10;
    strcpy(test_struct.name, "City Gloomy");

    pmq_send(&test_struct, sizeof(test_struct), PRIORITY_HIGH);
    printf("queue's node count: %d, size: %ld\n", list_node_count(pmq.queues[PRIORITY_FREE]), pmq.free_queue_size);

    memset(&test_struct, 0, sizeof(test_struct));
    test_struct.age = 15;
    strcpy(test_struct.name, "Bear Car");
    pmq_send(&test_struct, sizeof(test_struct), PRIORITY_MEDIUM);
    printf("queue's node count: %d, size: %ld\n", list_node_count(pmq.queues[PRIORITY_FREE]), pmq.free_queue_size);
    memset(&test_struct, 0, sizeof(test_struct));
    test_struct.age = 20;
    strcpy(test_struct.name, "Carrier");
    pmq_send(&test_struct, sizeof(test_struct), PRIORITY_LOW);
    printf("queue's node count: %d, size: %ld\n", list_node_count(pmq.queues[PRIORITY_FREE]), pmq.free_queue_size);

    while (1)
    {
        TestStruct_t test_struct_recv = {0};
        pmq_receive(&test_struct_recv);
        printf("Received message: %s, age: %d\n", test_struct_recv.name, test_struct_recv.age);
    }
}

static void test_pmq_init()
{
    pmq_init(&pmq);
}
static void output_pmsq_info()
{
    printf("queue's node count: %d, size: %ld\n", list_node_count(pmq.queues[PRIORITY_FREE]), pmq.free_queue_size);
}

static void test_send_receive()
{
    printf("********************************************************\n");
    printf("******************* test_send_receive ******************\n");
    printf("********************************************************\n");
    pmq_init(&pmq);
    printf("queue's node count: %d, size: %ld\n", list_node_count(pmq.queues[PRIORITY_FREE]), pmq.free_queue_size);

    pmq_send("Message 1", sizeof("Message 1"), PRIORITY_HIGH);
    printf("queue's node count: %d, size: %ld\n", list_node_count(pmq.queues[PRIORITY_FREE]), pmq.free_queue_size);
    pmq_send("Message 1.1", sizeof("Message 1.1"), PRIORITY_HIGH);
    printf("queue's node count: %d, size: %ld\n", list_node_count(pmq.queues[PRIORITY_FREE]), pmq.free_queue_size);
    pmq_send("Message 2", sizeof("Message 2"), PRIORITY_MEDIUM);
    printf("queue's node count: %d, size: %ld\n", list_node_count(pmq.queues[PRIORITY_FREE]), pmq.free_queue_size);

    char msg[256] = {0};
    pmq_receive(&msg);
    printf("Received message: %s\n", msg);

    memset(msg, 0, sizeof(msg));
    pmq_receive(&msg);
    printf("Received message: %s\n", msg);

    memset(msg, 0, sizeof(msg));
    pmq_receive(&msg);
    printf("Received message: %s\n", msg);

    memset(msg, 0, sizeof(msg));
    pmq_receive(&msg);
    printf("Received message: %s\n", msg);
}

static void test_dequeue()
{
    PriorityQueue pmq = {0};
    pmq_init(&pmq);
    printf("queue's node count: %d, size: %ld\n", list_node_count(pmq.queues[PRIORITY_FREE]), pmq.free_queue_size);

    pmq_enqueue(&pmq, "Message 1", sizeof("Message 1"), PRIORITY_HIGH);
    printf("queue's node count: %d, size: %ld\n", list_node_count(pmq.queues[PRIORITY_FREE]), pmq.free_queue_size);
    pmq_enqueue(&pmq, "Message 1.1", sizeof("Message 1.1"), PRIORITY_HIGH);
    printf("queue's node count: %d, size: %ld\n", list_node_count(pmq.queues[PRIORITY_FREE]), pmq.free_queue_size);
    pmq_enqueue(&pmq, "Message 2", sizeof("Message 2"), PRIORITY_MEDIUM);
    printf("queue's node count: %d, size: %ld\n", list_node_count(pmq.queues[PRIORITY_FREE]), pmq.free_queue_size);
    pmq_enqueue(&pmq, "Message 2.1", sizeof("Message 2.1"), PRIORITY_MEDIUM);
    printf("queue's node count: %d, size: %ld\n", list_node_count(pmq.queues[PRIORITY_FREE]), pmq.free_queue_size);
    pmq_enqueue(&pmq, "Message 2.2", sizeof("Message 2.2"), PRIORITY_MEDIUM);
    printf("queue's node count: %d, size: %ld\n", list_node_count(pmq.queues[PRIORITY_FREE]), pmq.free_queue_size);
    pmq_enqueue(&pmq, "Message 3", sizeof("Message 3"), PRIORITY_LOW);
    printf("queue's node count: %d, size: %ld\n", list_node_count(pmq.queues[PRIORITY_FREE]), pmq.free_queue_size);
    int priority = 0;
    int data_len = 0;
    for (int i = 0; i < 10; i++)
    {
        char *str = (char *)pmq_dequeue(&pmq, &data_len, &priority);
        printf("dequeue message: %s, data_len: %d, priority: %d\n", str, data_len, priority);
        // free(str);
        printf("queue's node count: %d, size: %ld\n", list_node_count(pmq.queues[PRIORITY_FREE]), pmq.free_queue_size);
    }
}

static void test_enqueue()
{
    PriorityQueue pmq;
    pmq_init(&pmq);
    pmq_enqueue(&pmq, "Message 1", sizeof("Message 1"), PRIORITY_HIGH);
    pmq_enqueue(&pmq, "Message 1.1", sizeof("Message 1.1"), PRIORITY_HIGH);
    pmq_enqueue(&pmq, "Message 2", sizeof("Message 2"), PRIORITY_MEDIUM);
    pmq_enqueue(&pmq, "Message 2.1", sizeof("Message 2.1"), PRIORITY_MEDIUM);
    pmq_enqueue(&pmq, "Message 2.2", sizeof("Message 2.2"), PRIORITY_MEDIUM);
    pmq_enqueue(&pmq, "Message 3", sizeof("Message 3"), PRIORITY_LOW);

    pmq_print(&pmq);
}

static void test_link()
{
    // QNode *head = NULL;

    // insert_tail(&head, 1);
    // insert_tail(&head, 2);
    // insert_tail(&head, 3);
    // insert_head(&head, 0);

    // printf("List: ");
    // print_list(head);

    // delete_node(&head, 2);
    // printf("After deleting 2: ");
    // print_list(head);

    // QNode *found = find_node(head, 3);
    // if (found)
    // {
    //     printf("Found node with data = %d\n", found->data);
    // }

    // free_list(head);
}
